/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


// cl_userCmd.cpp -- user command generation


#include "client.h"


/*
 ==================
 CL_SetUserCmdFlags

 Sets the userCmd_t flags
 ==================
*/
static void CL_SetUserCmdFlags (userCmd_t *userCmd){

	userCmd->flags = 0;

	// Let the game know if the player is running
	if (in_speed.active ^ in_alwaysRun->integerValue)
		userCmd->flags |= UCF_RUNNING;

	// Let the game know if the player is using the console or the GUI
	if (Key_GetCatchers())
		userCmd->flags |= UCF_TALKING;
}

/*
 ==================
 CL_SetButtonBits

 Sets the userCmd_t button bits
 ==================
*/
static void CL_SetButtonBits (userCmd_t *userCmd){

	int		i;

	userCmd->buttons = 0;

	// Send a button bit even if the key was pressed and released in less than
	// a frame
	for (i = 0; i < 32; i++){
		if (in_buttons[i].active || in_buttons[i].wasPressed)
			userCmd->buttons |= BIT(i);

		in_buttons[i].wasPressed = false;
	}
}

/*
 ==================
 CL_AdjustAngles

 Adjusts the local angles
 ==================
*/
static void CL_AdjustAngles (void){

	float	speed;

	if (in_speed.active ^ in_alwaysRun->integerValue)
		speed = MS2SEC(com_frameMsec) * in_angleSpeedKey->floatValue;
	else
		speed = MS2SEC(com_frameMsec);

	cl.viewAngles.pitch -= speed * in_pitchSpeed->floatValue * IN_KeyState(&in_lookUp);
	cl.viewAngles.pitch += speed * in_pitchSpeed->floatValue * IN_KeyState(&in_lookDown);

	if (!in_strafe.active){
		cl.viewAngles.yaw -= speed * in_yawSpeed->floatValue * IN_KeyState(&in_turnRight);
		cl.viewAngles.yaw += speed * in_yawSpeed->floatValue * IN_KeyState(&in_turnLeft);
	}
}

/*
 ==================
 CL_KeyMove

 Sets the userCmd_t based on key states
 ==================
*/
static void CL_KeyMove (userCmd_t *userCmd){

	float	speed;
	float	forwardMove = 0.0f;
	float	rightMove = 0.0f;
	float	upMove = 0.0f;

	// Adjust for speed key / running
	if (in_speed.active ^ in_alwaysRun->integerValue)
		speed = 127.0f;
	else
		speed = 64.0f;

	forwardMove += speed * IN_KeyState(&in_moveForward);
	forwardMove -= speed * IN_KeyState(&in_moveBack);

	rightMove += speed * IN_KeyState(&in_moveRight);
	rightMove -= speed * IN_KeyState(&in_moveLeft);

	if (in_strafe.active){
		rightMove += speed * IN_KeyState(&in_turnRight);
		rightMove -= speed * IN_KeyState(&in_turnLeft);
	}

	upMove += speed * IN_KeyState(&in_moveUp);
	upMove -= speed * IN_KeyState(&in_moveDown);

	userCmd->forwardMove = M_ClampChar(M_FtolFast(forwardMove));
	userCmd->rightMove = M_ClampChar(M_FtolFast(rightMove));
	userCmd->upMove = M_ClampChar(M_FtolFast(upMove));
}

/*
 ==================
 CL_MouseMove

 Sets the userCmd_t based on mouse states
 ==================
*/
static void CL_MouseMove (userCmd_t *userCmd){

	float	rate, accelSensitivity;
	float	mx, my;
	int		x = 0, y = 0;
	int		i, index;

	// Smooth mouse movement
	if (m_smooth->integerValue < 1)
		CVar_SetInteger(m_smooth, 1);
	else if (m_smooth->integerValue > MAX_MOUSE_SAMPLES)
		CVar_SetInteger(m_smooth, MAX_MOUSE_SAMPLES);

	for (i = 0; i < m_smooth->integerValue; i++){
		index = (MAX_MOUSE_SAMPLES - i + cl.mouseIndex) & (MAX_MOUSE_SAMPLES - 1);

		x += cl.mouseX[index];
		y += cl.mouseY[index];
	}

	cl.mouseIndex = (cl.mouseIndex + 1) & (MAX_MOUSE_SAMPLES - 1);

	cl.mouseX[cl.mouseIndex] = 0;
	cl.mouseY[cl.mouseIndex] = 0;

	mx = (float)x / m_smooth->integerValue;
	my = (float)y / m_smooth->integerValue;

	if (!mx && !my)
		return;

	// Apply sensitivity and acceleration
	rate = M_Sqrt(mx*mx + my*my) / (float)com_frameMsec;
	accelSensitivity = m_sensitivity->floatValue + rate * m_accel->floatValue;

	// Scale by game sensitivity
	accelSensitivity *= cl.gameSensitivity;

	if (m_showRate->integerValue && rate)
		Com_Printf("%.2f : %.2f\n", rate, accelSensitivity);

	mx *= accelSensitivity;
	my *= accelSensitivity;

	// Add mouse movement
	if (!mx && !my)
		return;

	if (m_showMove->integerValue)
		Com_Printf("%.2f %.2f\n", mx, my);

	if (!in_strafe.active && (in_mouseLook || in_freeLook->integerValue))
		cl.viewAngles.pitch += m_pitch->floatValue * my;
	else
		userCmd->forwardMove = M_ClampChar(userCmd->forwardMove - M_FtolFast(my * m_strafe->floatValue));

	if (!in_strafe.active)
		cl.viewAngles.yaw -= m_yaw->floatValue * mx;
	else
		userCmd->rightMove = M_ClampChar(userCmd->rightMove + M_FtolFast(mx * m_strafe->floatValue));
}

/*
 ==================
 CL_BuildUserCmd

 Builds a new userCmd_t for this frame
 ==================
*/
void CL_BuildUserCmd(void){

	userCmd_t	*userCmd;
	float		pitch;

	if (cls.state < CS_PRIMED)
		return;

	cl.userCmdNumber++;
	userCmd = &cl.userCmds[cl.userCmdNumber & USERCMD_MASK];

	// Save the current pitch
	pitch = cl.viewAngles.pitch;

	// Set the time
	userCmd->time = cl.time;

	// Set the flags
	CL_SetUserCmdFlags(userCmd);

	// Set the button bits
	CL_SetButtonBits(userCmd);

	// Keyboard angle adjustment
	CL_AdjustAngles();

	// Get basic movement from keyboard
	CL_KeyMove(userCmd);

	// Get basic movement from mouse
	CL_MouseMove(userCmd);

	// Check to make sure pitch hasn't wrapped
	if (cl.viewAngles.pitch - pitch > 90.0f)
		cl.viewAngles.pitch = pitch + 90.0f;
	else if (pitch - cl.viewAngles.pitch > 90.0f)
		cl.viewAngles.pitch = pitch - 90.0f;

	// Set the angles
	userCmd->angles[0] = ANGLE2SHORT(cl.viewAngles[0]);
	userCmd->angles[1] = ANGLE2SHORT(cl.viewAngles[1]);
	userCmd->angles[2] = ANGLE2SHORT(cl.viewAngles[2]);

	//send the command

}
